package modelo.gestionPedidos;

import java.io.Serializable;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.text.DateFormat;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import aplicaciones.Main;

import interfacesRemotas.*;
import modelo.gestionCarta.Producto;
import modelo.gestionFacturacion.Balance;
import modelo.gestionFacturacion.Factura;
import modelo.gestionFacturacion.LineaBalance;
import modelo.gestionReserva.Mesa;

/**
 * Con esta clase representamos una comanda o un pedido realizado por un camarero 
 * 
 * @created 02-may-2007 18:54 // 24-05-07 19:30
 * @version 1.0
 * @author Enrique Menor Santos, Carmen Pastor Morales, Ana Belén Pelegrina Ortiz
 */

public class Pedido implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 7484310957780739815L;
	/**
	 * código único de pedido
	 */
	private int codPedido;
	/**
	 * número de comensales que participan en ese pedido.
	 */
	private int numComensales;
	/**
	 * Lista de mesas asociadas al pedido
	 */
	private List<Mesa> listaMesas;
	/**
	 * Lista de consumiciones asociada al pedido
	 */
	private List<Consumicion> listaConsumiciones;
	
	/**
	 * Cadena que contiene la direccion ip donde se esta ejecutando el servidor RMI.
	 * Esto es necesario para la ejecucion de objetos remotos.
	 */
	String ipServidorRMI;
	
	private Factura factura;
	
	/**
	 * Constructor público: solo crea el objeto en memoria, no se accede a la base de datos
	 * Este constructor se utiliza desde las aplicaciones cliente para crear un nuevo
	 * pedido, por eso solo necesita la lista de mesas y el numero de comensales
	 * n Numero de comensales.
	 * m Lista de mesas usadas.
	 * ip Direccion ip del servidor RMI.
	 */
	
	public Pedido(int n, List<Mesa> m, String ip){
		this.ipServidorRMI = ip;
		if(n>0)
			this.numComensales = n;
		else
			this.numComensales = 0;
		
		if (m!= null)
			this.listaMesas=m;
		else
			this.listaMesas = new LinkedList<Mesa>(); //se crea una lista vacía para almacenar las mesas
		
		this.listaConsumiciones = new LinkedList<Consumicion>(); //se crea una lista vacía para almacenar las consumiciones
		
	}

	/**
	 * Constructor público: Este constructor se utiliza desde el lado servidor 
	 * para almacenar en memoria los datos de un pedido extraidos de la bd.
	 * Este constructor será ejecutado en el Servidor, por lo q el atributo ipServidorRMI será creado de forma automática
	 * usando la clase InetAdress.
	 *
	 */
	public Pedido(int cod, int n, List<Mesa> m, List<Consumicion> consu){
		this.codPedido=cod;
		this.numComensales=n;
		this.listaMesas=m;
		this.listaConsumiciones=consu;
		try {
			this.ipServidorRMI = InetAddress.getLocalHost().getHostAddress();
		} catch (UnknownHostException e) {
			//e.printStackTrace();
			System.err.println("Error en la clase Pedido al crear el pedido: " + e.getMessage());
		}
	}
	
	public float CalcularTotalPedido(){
		
		float totalConIVA = 0;
		float total = 0;
		
		for (int i=0; i<listaConsumiciones.size(); ++i) {
			totalConIVA += listaConsumiciones.get(i).getProducto().consultarPrecio();
			total += listaConsumiciones.get(i).getProducto().consultarPrecio() * 0.7;
		}
		
		return totalConIVA;
	}
	
	/**
	 * Este método crea el Pedido en la base de datos de forma permanente. 
	 * No tiene que comprobar que las mesas están disponibles o no reservadas pq
	 * se supone que esa comprobación se ha realizado arriba.
	 * 
	 * En caso contrario, o si ocurre un error al acceder a la BD, se devuelve falso
	 */
	public boolean insertarPedido(int idRestaurante)
	{
		InterfazPedido ap;
		boolean creado=false;
		try {
			 ap = (InterfazPedido)Naming.lookup("//" + this.ipServidorRMI + "/AccesoPedido");
			 creado = ap.insertPedido(idRestaurante,this.numComensales,this.listaMesas);
		} catch (RemoteException e) {
			System.err.println("Error en la clase Pedido en el metodo insertarPedido: " + e.getMessage());
		} catch (MalformedURLException e) {
			System.err.println("Error en la clase Pedido en el metodo insertarPedido: " + e.getMessage());
		} catch (NotBoundException e) {
			System.err.println("Error en la clase Pedido en el metodo insertarPedido: " + e.getMessage());
		} 
		return 	creado;	 
	}
	/**
	 * Método privado que se usa para que se actualicen los campos del pedido respecto a la  bd
	 * @return
	 */
	public boolean recuperarPedido()
	{
		Pedido p = null;
		InterfazPedido ap;
		boolean creado=false;
				
		try {
			ap = (InterfazPedido)Naming.lookup("//" + this.ipServidorRMI + "/AccesoPedido");
			p = ap.getPedido(this.codPedido);
			
		} catch (RemoteException e) {
			System.err.println("Error en la clase Pedido en el metodo recuperarPedido: " + e.getMessage());
		} catch (MalformedURLException e) {
			System.err.println("Error en la clase Pedido en el metodo recuperarPedido: " + e.getMessage());
		} catch (NotBoundException e) {
			System.err.println("Error en la clase Pedido en el metodo recuperarPedido: " + e.getMessage());
		}
		if(p==null)
			return false;
		else
		{
			this.numComensales=p.getNumComensales();
			this.listaConsumiciones=p.getConsumiciones();
			this.listaMesas=p.getMesas();
		}
		return 	creado;		
	}
	/**
	 * Actualiza los datos de un pedido existente en la bd,
	 *  pudiendo ampliar el número de comensales, la lista de consumiciones o la lista de mesas
	 * @return cierto si se ha podido actualizar, o falso en caso contrario
	 */
	public boolean actualizarPedido()
	{
		InterfazPedido ap;
		boolean guardado=false;
		try {
			ap = (InterfazPedido)Naming.lookup("//" + this.ipServidorRMI + "/AccesoPedido");
			guardado = ap.ampliarPedido(this.codPedido, this.numComensales, this.listaMesas, this.listaConsumiciones);
		} catch (RemoteException e) {
			System.err.println("Error al conectar con la clase remota AccesoPedido desde el metodo guardarPedido de Pedido: " + e.getMessage());
		} catch (MalformedURLException e) {
			System.err.println("Error en la clase Pedido en el metodo actualizarPedido: " + e.getMessage());
		} catch (NotBoundException e) {
			System.err.println("Error en la clase Pedido en el metodo actualizarPedido: " + e.getMessage());
		}
		return 	guardado;		
	}
	/**
	 * Este método busca y elimina el pedido de la BD.
	 * Todas las mesas asociadas al pedido será marcadas como libres en la BD.
	 * Tambien se eliminarám todas las consumiciones asociadas al pedido (si las tiene).
	 * @return cierto si se ha eliminado con éxito, falso en caso contrario
	 */
	public boolean eliminarPedido()
	{
		InterfazPedido ap;
		boolean borrado = false;
		//antes de borrar el pedido hay que borrar sus consumiciones
		//esto se borran de forma automatica en la BD, pero hay que borrarlos de memoria
		this.listaConsumiciones=null;
		this.listaMesas=null;
		//busca el registro en la tabla de pedidos y lo elimina
		try {
			ap = (InterfazPedido)Naming.lookup("//" + this.ipServidorRMI + "/AccesoPedido");
			borrado = ap.deletePedido(this.codPedido);
		} catch (RemoteException e) {
			System.err.println("Error al conectar con la clase remota AccesoPedido desde el metodo guardarPedido de Pedido: " + e.getMessage());
		} catch (MalformedURLException e) {
			System.err.println("Error en la clase Pedido en el metodo eliminarPedido: " + e.getMessage());
		} catch (NotBoundException e) {
			System.err.println("Error en la clase Pedido en el metodo eliminarPedido: " + e.getMessage());
		}
		return borrado;
	}
	/**
	 * Borra todas las consumiciones del pedido.
	 * Es un método privado que sólo puede ser llamado por 'eliminarPedido'
	 * @return cierto si se consiguen borrar todas las consumiciones
	 */
	/*
	private boolean borrarConsumiciones()
	{
		boolean eliminadas = true;
		ListIterator<Consumicion> it =  this.listaConsumiciones.listIterator();
		//recorremos la lista de consumiciones y borramos una por una
		while(!eliminadas && it.hasNext())
			eliminadas = ((Consumicion)it.next()).eliminarConsumicion();
		
		return eliminadas;
	}*/
	/**
	 * Agrega una mesa al pedido.
	 * Para hacer permanentes los cambios llamar a actualizarPedido
	 * @param m: mesa a agregar
	 *
	 */
	public void agregarMesa(Mesa m)
	{
		this.listaMesas.add(m);		
	}
	/**
	 * Agrega una lista de mesas al pedido
	 * Para hacer permanentes los cambios llamar a actualizarPedido
	 * @param mesas
	 *
	 */
	public void agregarMesas(List<Mesa> mesas)
	{
		this.listaMesas.addAll(mesas);		
	}
	/**
	 * Método que busca una consumicion dentro del pedido dado su codigo
	 * @param codConsumicion
	 * @return el objeto Consumicion si lo ha encontrado, null en caso contrario
	 */
	public Consumicion getConsumicion(int codConsumicion)
	{
		Consumicion c;
		ListIterator<Consumicion> it= this.listaConsumiciones.listIterator();
		
		while (it.hasNext())
		{
			c=it.next();
			if (c.getCodConsumicion() == codConsumicion)
				return c;				
		}		
		//si se sale del bucle es que no se ha 		
		return null;
	}
	/**
	 * Método que devuelve la lista de consumiciones asociadas al pedido
	 * @return la lista de consumiciones asociadas al pedido
	 */
	public List<Consumicion> getConsumiciones()
	{
		return this.listaConsumiciones;
	}
	/**
	 * /**
	 * Agregra una consumición al pedido. 
	 * Hay que obtener la seccion y el producto de la base de datos
	 * No se comprueba que el producto pertenece a la seccion pq eso ya se hace desde la GUI
	 * @param s
	 * @param p
	 * @param numComensal
	 * @param estado
	 * @param observaciones
	 * @return cierto si se consigue agregar la consumicion al pedido
	 */
	public boolean anadirConsumicion(Producto producto,int numComensal, int estado, String observaciones)
	{
		//El número de comensal es >= 0 y <= al número de comensales del pedido.
		if (!(numComensal>=0 && numComensal<=this.numComensales))
			return false;
		
		// creamos la consumicione en memoria
		Consumicion c = new Consumicion(producto,estado,numComensal,observaciones,this.ipServidorRMI);
		
		//y de forma permanente en la BD
		if (c.crearConsumicion(this.codPedido))
		{
			
			this.listaConsumiciones.add(c);
			this.actualizarPedido();
			return true;
		}
		return false;
	}
	/**
	 * Elimina una consumición y su asociación con el pedido dado.
	 * La consumición no puede estár en “preparación”, “lista” o “servida”.
	 * @param c
	 * @return
	 */
	public boolean eliminarConsumicion(Consumicion c)
	{
		//comprobamos el estado de la consumicion
		if (c.getEstado()==Consumicion.EN_PREPARACION || c.getEstado()==Consumicion.LISTO || c.getEstado() == Consumicion.SERVIDO)
			return false;
		//la eliminamos de la BD
		if (!c.eliminarConsumicion())
			return false;
		//la eliminamos de la lista
		this.listaConsumiciones.remove(c);
				
		return true;		
	}
	/**
	 * Modifica un objeto consumición asignándole los atributos que se han dado.
	 * OJO!!Se supone que la Consumicion  y el producto existe en el sistema
	 * Esto debe garantizarlo la GUI, se hace así para mejorar el rendimiento del sistema
	 * @param c
	 * @param p
	 * @param numComensal
	 * @param estado
	 * @param observaciones
	 * @return cierto si se ha conseguido modificar la consumicion, falso en caso contrario
	 */
	public boolean modificarConsumicion(Consumicion c, Producto p,int numComensal,int estado, String observaciones)
	{
		if (c==null)
			return false;
		//El número de comensal es >= 0 y <= al número de comensales del pedido.
		if (!(numComensal>=0 && numComensal<=this.numComensales))
			return false;
		//ahora modificamos los parametros uno por uno
		c.setProducto(p);
		c.setNumComensal(numComensal);
		c.setEstado(estado);
		c.setObservaciones(observaciones);
		c.actualizarConsumicion();
		return true;		
	}
	/**
	 * Se cambia la observación que tenga la consumición determinada por la que se pasa como parámetro
	 * @param c
	 * @param obs
	 * @return cierto si se ha conseguido modificar, false en caso contrario
	 */
	public boolean especificarObservaciones(Consumicion c, String obs)
	{
		if (c == null)
			return false;
		this.getConsumicion(c.getCodConsumicion()).setObservaciones(obs);
		
		return true;
	}
	
	/**
	 * Recupera la lista de observaciones predefinidas que estan
	 * almacenadas en la bd
	 */
	public List<String> recuperarObservaciones()
	{
		InterfazObservaciones ao;
		List<String> observaciones= new LinkedList<String>();
		try {
			ao = (InterfazObservaciones)Naming.lookup("//"+this.ipServidorRMI+"/AccesoObservaciones");
			observaciones=ao.getObservaciones();
		} catch (RemoteException e) {
			System.err.println("Error al conectar con la clase remota AccesoObservaciones desde el metodo recuperarObservaciones de Pedido: " + e.getMessage());
		} catch (MalformedURLException e) {
			System.err.println("Error en la clase Pedido en el metodo recuperarObservaciones: " + e.getMessage());
		} catch (NotBoundException e) {
			System.err.println("Error en la clase Pedido en el metodo recuperarObservaciones: " + e.getMessage());
		}
		return observaciones;
	}
	/**
	 * Inserta una observacion nueva en la base de datos
	 */
	public boolean agregarObservaciones(String observacion)
	{
		InterfazObservaciones ao;
		boolean insertada=false;
			try {
				ao = (InterfazObservaciones)Naming.lookup("//"+this.ipServidorRMI+"/AccesoObservaciones");
				insertada=ao.insertObservacion(observacion);
			} catch (RemoteException e) {
				System.err.println("Error al conectar con la clase remota AccesoObservaciones desde el metodo agregarObservaciones de Pedido: " + e.getMessage());
			} catch (MalformedURLException e) {
				System.err.println("Error en la clase Pedido en el metodo agregarObservaciones: " + e.getMessage());
			} catch (NotBoundException e) {
				System.err.println("Error en la clase Pedido en el metodo agregarObservaciones: " + e.getMessage());
			}
			return insertada;
	}
	/**
	 * 
	 * @return las mesas asociadas al pedido
	 */
	public List<Mesa> getMesas()
	{
		return this.listaMesas;
	}
	/**
	 * Se entiende que la mesa titular del pedido es la primera de la lista, 
	 * es decir, aquella que ocupa la posicion 0
	 * @return la mesa titular del pedido
	 */
	public Mesa getMesaTitular()
	{
		return (Mesa)((LinkedList)this.listaMesas).getFirst();
	}
	
	/**
	 * Método que comprueba si la mesa pertenece al pedido y en ese caso la libera
	 * @param m
	 * @return cierto si se ha podido liberar la mesa del pedido
	 * @deprecated Este método se ha creado pero se va utilizar desde la GUI
	 */
	public boolean liberarMesa(Mesa m)
	{
		if (this.listaMesas.contains(m))
		{
			this.listaMesas.remove(m);
			m.cambiarEstado("libre");
			return actualizarPedido();
		}
		return false;
	}
	/**
	 * Método que libera todas las mesas del pedido
	 * @return cierto si se ha podido liberar las mesas del pedido
	 * @deprecated Este método se ha creado, pero no se se va utilizar porque la liberacion de las
	 * mesas se hace a mas bajo nivel
	 */
	public boolean liberarMesas()
	{
		ListIterator<Mesa> it = this.listaMesas.listIterator();
		while(it.hasNext())
			((Mesa)it.next()).cambiarEstado("libre");
		this.listaMesas.clear();
		
		return actualizarPedido();
	}
		
	/**
	 * Sirve para buscar métodos con un codigo concreto
	 * @return el código del pedido
	 */
	public int getCodPedido()
	{
		return this.codPedido;
	}
	/**
	 * 
	 * @return el numero de comensales del pedido
	 */
	public int getNumComensales ()
	{
		return this.numComensales;
	}
	/**
	 * El número nuevo de comensales tiene que ser mayor que 0
	 * @param numComensales
	 * @return cierto si el nuevo número de comensales no es correcto
	 */
	public boolean setNumComensales(int numComensales)
	{
		
		if (numComensales<=0)
			return false;
		this.numComensales=numComensales;
		
		return true;
	}
	/**
	 * Cambia el estado de la consumición a “solicitado” y se avisa a cocina.
	 * (el aviso a cocina debería hacerse dentro del método setEstado de Consumicion)
	 * Debe existir la consumición en el sistema.
	 * El estado de la consumición no puede ser “pedida barra”, es decir, es un plato de cocina.
	 * @param c
	 * @return
	 */
	public boolean solicitarPlatoRetrasado(Consumicion c)
	{
		
		if (c!=null || c.getEstado() == Consumicion.RETRASADO )
			c.setEstado(Consumicion.SOLICITADO);
		else
			return false;
		c.actualizarConsumicion();
		return true;
	}
	/**
	 * Marca el estado de las consumiciones seleccionadas como “servidas”.
	 * Esto es debido a que las bebidas y demas productos de barra se sirven de forma inmediata.
	 * Se supone que las conusmiciones pasadas como parametros pertenecen al pedido, 
	 * la GUI se encargará de eso
	 * @param consumiciones
	 * @return cierto si se han conseguido marcar
	 * @deprecated
	 */
	public boolean servirPedidoBarra(List<Consumicion> consumiciones )
	{
		if (consumiciones == null)
			return false;
		ListIterator<Consumicion> it = consumiciones.listIterator();
		while (it.hasNext())
			it.next().setEstado(Consumicion.SERVIDO);
		return true;
	}
	/**
	 * Método que devuelve una lista con todas las consumiciones que actualmente
	 * tengan el estado 'listo'.
	 * Se hace internamente accediendo con una cosulta a la BD.
	 * En caso de error, se devuelve null
	 * @return
	 */
	public List <Consumicion> recuperarPlatosListos ()
	{
		List <Consumicion> platosListos=null ;
		InterfazPedido ap;
		try {
			ap = (InterfazPedido)Naming.lookup("//" + this.ipServidorRMI + "/AccesoPedido");
			platosListos = ap.getConsumicionesListas(this.codPedido) ;
		} catch (RemoteException e) {
			System.err.println("Error RemoteException con la clase remota AccesoPedido desde el metodo recuperarPlatosListos de Pedido: " + e.getMessage());
		} catch (MalformedURLException e) {
			System.err.println("Error MalformedURLException en la clase Pedido en el metodo recuperarPlatosListos: " + e.getMessage());
		} catch (NotBoundException e) {
			System.err.println("Error NotBoundException en la clase Pedido en el metodo recuperarPlatosListos: " + e.getMessage());
		}
		return platosListos;
		
		
	}
	/**
	 * Marca el estado de las consumiciones seleccionadas como “servidas”.
	 * Debe existir la/s consumición/es en el sistema.
	 * El estado de las consumiciones debe ser “lista”: se comprueba por robustez,
	 * pero se supone que la GUI controla eso
	 * @param consumiciones
	 * @return cierto si se ha modificado el 
	 */
	public boolean servirPlatos(List<Consumicion> consumiciones )
	{
		if (consumiciones == null)
			return false;
		
		boolean todosServidor=true;
		ListIterator<Consumicion> it = consumiciones.listIterator();
		while (it.hasNext())
		{
			Consumicion c=it.next();
			if(c.getEstado()==Consumicion.LISTO)
			{
				c.setEstado(Consumicion.SERVIDO);
				c.actualizarConsumicion();
			}
			else 
				todosServidor=false;
		}
		return todosServidor;
	}
	
	@SuppressWarnings("deprecation")
	public void ConfirmarFactuar(){
		/*
		 * 1) si no existe Balance, crearlo
		 * 2) buscamos el pedido
		 * 3) por cada consumición del pedido buscamos si existe alguna entrada para el producto
		 * 		a) si existe incrementamos la cantidad y el total almacenado
		 * 		b) en caso contrario creamos una nueva linea de balance para el producto
		 * 4) Guardamos los cambios del balance en la BD
		 * 5) Imprimir la factura, x duplicado
		 */
		
		//TODO poner el año actual
		
		int year = DateFormat.getDateInstance().getCalendar().getTime().getYear() + 1979 + 1;
		Balance balance = Main.m_Restaurante.getBalance(year);
		
		if (balance != null){
			balance = new Balance();
		}
		
		for (int i=0; i<listaConsumiciones.size(); ++i){
			Producto p =listaConsumiciones.get(i).getProducto();
			
			int pos_linea = balance.existeProducto( p.consultarIdProducto() ); 
			
			if (pos_linea != -1)
				balance.getLineaBalance(pos_linea).incrementarLinea(p.consultarPrecio());
			else 
				balance.agregarLineaBalance(new LineaBalance(p));
		}
		
		// TODO guardar los cambios del balance en la BD vía RMI
		balance.actualizarBD();
	}

}
